package mgo

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// 参考文档1：https://juejin.cn/post/6844904035862970381
// 参考文档2：https://www.cnblogs.com/zhoujie/p/mongo1.html
// 参考文档3：https://www.cnblogs.com/xuliuzai/p/10055535.html

type AggregateSymbol string

const (
	Avg   AggregateSymbol = "$avg"   // 计算平均值，bson.D{{"$group", bson.D{{"AliasFieldName", bson.D{{"$avg","$fieldName"}}}}}}
	First AggregateSymbol = "$first" // 返回每组第⼀个⽂档，如果有排序，按照排序，如果没有按照默认的存储的顺序的第⼀个⽂档，bson.D{{"$group", bson.D{{"AliasFieldName", bson.D{{"$first","$fieldName"}}}}}}
	Last  AggregateSymbol = "$last"  // 返回每组最后⼀个⽂档，如果有排序，按照排序，如果没有按照默认的存储的顺序的最后个⽂档，bson.D{{"$group", bson.D{{"AliasFieldName", bson.D{{"$last","$fieldName"}}}}}}
	Max   AggregateSymbol = "$max "  // 根据分组，获取集合中所有⽂档对应值得最⼤值，bson.D{{"$group", bson.D{{"AliasFieldName", bson.D{{"$max","$fieldName"}}}}}}
	Min   AggregateSymbol = "$min"   // 根据分组，获取集合中所有⽂档对应值得最⼩值，bson.D{{"$group", bson.D{{"AliasFieldName", bson.D{{"$min","$fieldName"}}}}}}
	Sum   AggregateSymbol = "$sum"   // 计算总和，bson.D{{"$group", bson.D{{"AliasFieldName", bson.D{{"$sum",1}}}}}}
)

type AggregateInfo struct {
	Collection       *mongo.Collection
	Ctx              context.Context
	GroupField       []string // 用于指定分组的字段，有序的，如根据 sex+age 分组 []string{"sex","age"}，和 age+sex 分组注入：[]string{"sex","age"} 效果是不一致的
	GroupOperation   []*GroupOperationInfo
	Match            *MatchInfo
	NeedId           int            // 设置是否需要返回 _id 字段，0-不返回（默认）；1-返回
	Field            []string       // 设置返回的字段信息，注意：只能设置 GroupOperation 中设置的字段别名，如果没有设置，默认返回聚合的所有字段
	Sort             []string       // 排序字段，如：[]string{"age","-time"}，按照 age 正向排序，当 age 相同时按照 time 逆向排序
	Skip             int64          // 查询结果跳过数量
	Limit            int64          // 限制查询结果数量
	Pipeline         mongo.Pipeline // 自定义查询管道
	Result           interface{}    // 结果数据存储地址
	AggregateOptions *options.AggregateOptions
}

// GroupOperationInfo 用于指定分组字段信息
type GroupOperationInfo struct {
	AliasName string       // 指定的聚合的字段别名
	Symbol    FilterSymbol // 指定聚合操作符号
	FieldName string       // 指定操作字段名称，比如 name、age
}

// MatchInfo 用于指定条件字段信息
type MatchInfo struct {
	One *FilterUnit     // 单个字段复杂匹配条件：bson.M{"name": bson.M{"$ne": "Tom", "$exists": true}}
	And []*FilterUnit   // bson.M{"name": "Jimmy Kuu", "age": 33}、bson.M{"name": bson.M{"$eq", "Jimmy Kuu"}, "age": bson.M{"$eq", 33}}
	Not []*FilterUnit   // bson.M{"$not": bson.M{"name": "Jimmy Kuu", "age": 31}}
	Or  [][]*FilterUnit // bson.M{"$or": []bson.M{bson.M{"name": "Jimmy Kuu"}, bson.M{"age": 31}}}
	Nor [][]*FilterUnit // bson.M{"$nor": []bson.M{bson.M{"name": "Jimmy Kuu"}, bson.M{"age": 31}}}
}

// Aggregate 数据聚合搜索
func Aggregate(info *AggregateInfo) (err error) {
	if info.Collection == nil || info.Result == nil {
		return errors.New("collection/result cannot be empty")
	}
	if info.Ctx == nil {
		info.Ctx = context.Background()
	}
	if info.Pipeline == nil {
		if err = info.buildPipeline(); err != nil {
			return err
		}
	}
	fmt.Println(info.Pipeline)
	return info.action()
}

func (info *AggregateInfo) buildPipeline() error {
	if len(info.GroupField) == 0 {
		return errors.New("group field cannot be empty")
	}
	// 设置分组字段信息
	var id bson.D
	for _, v := range info.GroupField {
		id = append(id, bson.E{v, "$" + v})
	}
	// 设置分组信息
	var group bson.D
	field := make([]string, 0)
	group = append(group, bson.E{"_id", id})
	for _, v := range info.GroupOperation {
		var value interface{}
		if v.FieldName != "" {
			value = "$" + v.FieldName
		} else {
			value = 1
		}
		group = append(group, bson.E{v.AliasName, bson.D{{string(v.Symbol), value}}})
		field = append(field, v.AliasName)
	}
	info.Pipeline = append(info.Pipeline, bson.D{{"$group", group}})
	// 设置条件信息
	if info.Match != nil {
		filter := &Filter{
			One: info.Match.One,
			And: info.Match.And,
			Not: info.Match.Not,
			Or:  info.Match.Or,
			Nor: info.Match.Nor,
		}
		match := filter.BuildFilterBsonD()
		info.Pipeline = append(info.Pipeline, bson.D{{"$match", match}})
	}
	// 设置返回字段信息
	var project bson.D
	project = append(project, bson.E{"_id", info.NeedId})
	if len(info.Field) == 0 {
		info.Field = field
	}
	for _, v := range info.Field {
		project = append(project, bson.E{v, 1})
	}
	info.Pipeline = append(info.Pipeline, bson.D{{"$project", project}})
	// 设置排序信息
	if len(info.Sort) != 0 {
		var s bson.D
		for _, v := range info.Sort {
			if v[0:1] == "-" {
				s = append(s, bson.E{v[1:len(v)], -1})
			} else {
				s = append(s, bson.E{v, 1})
			}
		}
		info.Pipeline = append(info.Pipeline, bson.D{{"$sort", s}})
	}
	// 设置 skip
	info.Pipeline = append(info.Pipeline, bson.D{{"$skip", info.Skip}})
	// 设置 Limit
	if info.Limit != 0 {
		info.Pipeline = append(info.Pipeline, bson.D{{"$limit", info.Limit}})
	}
	return nil
}

func (info *AggregateInfo) action() error {
	cursor, err := info.Collection.Aggregate(info.Ctx, info.Pipeline, info.AggregateOptions)
	if err != nil {
		return err
	}
	return cursor.All(info.Ctx, info.Result)
}
